﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Text;

using BizElements.Core;
using BizElements.BusinessLayer;

namespace BizElements.Web
{
    /// <summary>Creates common command objects.</summary>
    public static class CommandFactory
    {
        #region Cancel.

        /// <summary>Creates "Cancel" command which redirects user to the page specified in "onCloseUrl" parameter or to default/intro if URL is not defined.</summary>
        public static RedirectCommand CreateCancelCommand()
        {
            string cancelUrl = CommonQueryValues.OnCloseUrl ?? WebFormContext.DefaultPageRelativeUrl;
            return new RedirectCommand("Cancel", UiMessages.CancelText, cancelUrl);
        }

        #endregion

        #region Open.

        /// <summary>Creates "Accept" redirect command that open the selected object in a dedicated form. User must have permission for the specified action/function ID.</summary>
        public static RedirectCommand CreateOpenCommand(string relativeFormUrl, IDbColumn pk, IBusinessObjectDescriptor descriptor)
        {
            return CreateOpenCommand(relativeFormUrl, pk, descriptor, new IDbColumn[0]);
        }

        /// <summary>Creates "Accept" redirect command that open the selected object in a dedicated form. User must have permission for the specified action/function ID.</summary>
        public static RedirectCommand CreateOpenCommand(string relativeFormUrl, IDbColumn pk, IBusinessObjectDescriptor descriptor, IEnumerable<IDbColumn> additionalParameterColumns)
        {
            StringBuilder openUrl = new StringBuilder(relativeFormUrl);
            if (relativeFormUrl.Contains("?"))
                openUrl.Append("&");
            else
                openUrl.Append("?");

            openUrl.Append(CommonQueryKeys.EntityId);
            openUrl.Append("={0}");

            List<string> requiredParams = new List<string>() { pk.ColumnName };
            int nextRequiredParamIndex = 1;
            IDbColumn subtypeIdColumn = (descriptor.Fields.SubtypeIdPropertyName != null) ? pk.Table.Columns.GetByPropertyName(descriptor.Fields.SubtypeIdPropertyName) : null;
            if (subtypeIdColumn != null)
            {
                openUrl.Append("&");
                openUrl.Append(subtypeIdColumn.PropertyName);
                openUrl.Append("={1}");
                requiredParams.Add(subtypeIdColumn.ColumnName);
                nextRequiredParamIndex++;
            }

            if (additionalParameterColumns != null)
            {
                foreach (IDbColumn additionalDataParam in additionalParameterColumns)
                {
                    openUrl.Append("&");
                    openUrl.Append(additionalDataParam.PropertyName);
                    openUrl.Append("={");
                    openUrl.Append(nextRequiredParamIndex);
                    openUrl.Append("}");
                    requiredParams.Add(additionalDataParam.ColumnName);
                    nextRequiredParamIndex++;
                }
            }

            RedirectCommand accept = new RedirectCommand("Accept", UiMessages.OpenText, openUrl.ToString(), requiredParams.ToArray());
            accept.CurrentUrlQueryKey = CommonQueryKeys.OnCloseUrl;
            accept.RequiredPermission = descriptor.Permissions.ReadPermission;
            return accept;
        }

        /// <summary>Creates "Accept" redirect command that open the selected object in a dedicated form. User must have permission for the specified action/function ID.</summary>
        public static RedirectCommand CreateOpenCommand(string relativeFormUrl, IDbTable table, IBusinessObjectDescriptor descriptor)
        {
            return CreateOpenCommand(relativeFormUrl, table, descriptor, null);
        }

        /// <summary>Creates "Accept" redirect command that open the selected object in a dedicated form. User must have permission for the specified action/function ID.</summary>
        public static RedirectCommand CreateOpenCommand(string relativeFormUrl, IDbTable table, IBusinessObjectDescriptor descriptor, IEnumerable<IDbColumn> additionalParameterColumns)
        {
            List<string> requiredParams = new List<string>();

            StringBuilder openUrl = new StringBuilder(relativeFormUrl);
            char nextUrlParamDelimiter = relativeFormUrl.Contains("?") ? '&' : '?';
            DbColumnCollection pkFields = table.PrimaryKey;
            for (int i = 0; i < pkFields.Count; i++ )
            {
                requiredParams.Add(pkFields[i].ColumnName);

                openUrl.Append(nextUrlParamDelimiter);
                openUrl.Append(pkFields[i].PropertyName);
                openUrl.Append('=');
                openUrl.Append('{');
                openUrl.Append(i);
                openUrl.Append('}');
                nextUrlParamDelimiter = '&';
            }

            IDbColumn subtypeIdColumn = (descriptor.Fields.SubtypeIdPropertyName != null) ? table.Columns.GetByPropertyName(descriptor.Fields.SubtypeIdPropertyName) : null;
            if (subtypeIdColumn != null)
            {
                openUrl.Append("&");
                openUrl.Append(subtypeIdColumn.PropertyName);
                int subtypeParamIndex = requiredParams.Count;
                openUrl.Append("={" + subtypeParamIndex + "}");
                requiredParams.Add(subtypeIdColumn.ColumnName);
            }

            int additionalParamIndex = requiredParams.Count;
            if (additionalParameterColumns != null)
            {
                foreach (IDbColumn additionalDataParam in additionalParameterColumns)
                {
                    openUrl.Append("&");
                    openUrl.Append(additionalDataParam.PropertyName);
                    openUrl.Append("={");
                    openUrl.Append(additionalParamIndex);
                    openUrl.Append("}");
                    requiredParams.Add(additionalDataParam.ColumnName);
                    additionalParamIndex++;
                }
            }

            RedirectCommand accept = new RedirectCommand("Accept", UiMessages.OpenText, openUrl.ToString(), requiredParams.ToArray());
            accept.CurrentUrlQueryKey = CommonQueryKeys.OnCloseUrl;
            accept.RequiredPermission = descriptor.Permissions.ReadPermission;
            return accept;
        }

        /// <summary>Creates "Accept" redirect command that open the selected object in a dedicated form. User must have permission for the specified action/function ID.</summary>
        [Obsolete("Use method overloads that accept IBusinessObjectDescriptor object as input argument.")]
        public static RedirectCommand CreateOpenCommand(object requiredPermissionForActionId, string relativeFormUrl, IDbColumn pk)
        {
            string openUrl;
            if (relativeFormUrl.Contains("?"))
                openUrl = relativeFormUrl + "&" + CommonQueryKeys.EntityId + "={0}";
            else
                openUrl = relativeFormUrl + "?" + CommonQueryKeys.EntityId + "={0}";

            RedirectCommand accept = new RedirectCommand("Accept", UiMessages.OpenText, openUrl, new string[] { pk.ColumnName });
            accept.CurrentUrlQueryKey = CommonQueryKeys.OnCloseUrl;
            accept.RequiredPermission = requiredPermissionForActionId;
            return accept;
        }

        /// <summary>Creates "Accept" redirect command that open the selected object in a dedicated form. User must have permission for the specified action/function ID.</summary>
        [Obsolete("Use method overloads that accept IBusinessObjectDescriptor object as input argument.")]
        public static RedirectCommand CreateOpenCommand(object requiredPermissionForActionId, string relativeFormUrl, IDbColumn[] paramsCol)
        {
            string[] paramsStr = new string[paramsCol.Length];
            for (int i = 0; i < paramsCol.Length; i++)
                paramsStr[i] = paramsCol[i].ColumnName;

            RedirectCommand accept = new RedirectCommand("Accept", UiMessages.OpenText, relativeFormUrl, paramsStr);
            accept.CurrentUrlQueryKey = CommonQueryKeys.OnCloseUrl;
            accept.RequiredPermission = requiredPermissionForActionId;
            return accept;
        }

        #endregion

        #region New.

        /// <summary>Creates "New" command which redirects user to a dedicated form to create a new object. User must have permission for the specified action/function ID.</summary>
        public static RedirectCommand CreateNewCommand(string relativeFormUrl, IBusinessObjectDescriptor descriptor)
        {
            return CreateNewCommand(descriptor.Permissions.CreatePermission, relativeFormUrl, /*default text*/ null);
        }

        /// <summary>Creates "New" command which redirects user to a dedicated form to create a new object. User must have permission for the specified action/function ID.</summary>
        public static RedirectCommand CreateNewCommand(object requiredPermissionForActionId, string relativeFormUrl)
        {
            return CreateNewCommand(requiredPermissionForActionId, relativeFormUrl, /*default text*/ null);
        }

        /// <summary>Creates "New" command which redirects user to a dedicated form to create a new object. User must have permission for the specified action/function ID.</summary>
        public static RedirectCommand CreateNewCommand(object requiredPermissionForActionId, string relativeFormUrl, string commandText)
        {
            if (string.IsNullOrEmpty(commandText))
                commandText = UiMessages.NewRecordText;

            RedirectCommand newObject = new RedirectCommand("New", commandText, relativeFormUrl) 
            { 
                CurrentUrlQueryKey = CommonQueryKeys.OnCloseUrl, 
                RequiredPermission = requiredPermissionForActionId 
            };

            return newObject;
        }

        #endregion

        #region CreateNewFromLookup.

        const string JsReloadOpenerAndCloseCurrWindow =
            "opener.window.location.reload();" +
            "close();";

        /// <summary>Creates "New" command which opens a popup form that creates a new object/item. After the popup is closed, opener is reloaded. User must have permission for the specified action/function ID.</summary>
        public static JsCommand CreateNewFromLookup(object requiredPermissionForActionId, string newItemRelativeFormUrl, string commandText)
        {
            Page page = (Page)HttpContext.Current.Handler;
            if (string.IsNullOrEmpty(commandText))
                commandText = UiMessages.NewRecordText;

            string urlNew = page.ResolveUrl(newItemRelativeFormUrl);
            urlNew = WebUtil.SetUrlParam(urlNew, CommonQueryKeys.OnCloseScript, JsReloadOpenerAndCloseCurrWindow);
            JsCommand newItem = new JsCommand("New", commandText, JScript.RenderOpenWindow(urlNew, 800, 600, 100, 100, true));
            return newItem;
        }

        #endregion

        #region Deactivate composite entity via factory.

        /// <summary>Creates deactivate/delete command that initializes a business object using factory class and then calls its Deactivate() method. Uses object's GrantDeactivate() method to enforce security.</summary>
        public static BusinessObjectFactoryCommand<TBizObject, TFactory> CreateDeactivateCommand<TBizObject, TFactory>(IBusinessObjectDescriptor descriptor, object typeIdExample)
            where TBizObject : class, IBusinessObject
            where TFactory : IBusinessObjectFactory<TBizObject>, new()
        {
            var deactivate = new BusinessObjectFactoryCommand<TBizObject, TFactory>("Deactivate", UiMessages.DeactivateText);
            deactivate.RequiredPermission = descriptor.Permissions.DeletePermission;

            TFactory factory = new TFactory();
            IDbTable table = factory.GetBaseClassTable();
            foreach (IDbColumn pkPart in table.PrimaryKey)
                deactivate.MapProperty(pkPart.ColumnName, pkPart.PropertyName);
            
            string subtypeIdPropertyName = factory.SubtypeIdPropertyName;
            if (!string.IsNullOrEmpty(subtypeIdPropertyName))
                deactivate.MapSubtypeIdParameter(subtypeIdPropertyName);

            // It doesn't matter which subtype is returned by factory. We only need it to create delegates, concrete instance won't be used.
            TBizObject bizObject = factory.NewBusinessObject(null, typeIdExample);
            Type objType = typeof(TBizObject);
            Delegate grantDeactivateMethod = Delegate.CreateDelegate(typeof(Delegates.Function<BusinessRuleCollection, IActor>), bizObject, objType.GetMethod("GrantDeactivate"));
            deactivate.SetGrantExecuteMethod((Delegates.Function<BusinessRuleCollection, IActor>)grantDeactivateMethod);
            Delegate deactivateMethod = Delegate.CreateDelegate(typeof(Delegates.Procedure<IActor, bool>), bizObject, objType.GetMethod("Deactivate"));
            deactivate.SetExecuteMethod<bool>((Delegates.Procedure<IActor, bool>)deactivateMethod, false);

            deactivate.PrependToExecuteOnClient = "if (confirm('" + JScript.CleanString(UiMessages.AreYouSure) + "')){ ";
            deactivate.AppendToExecuteOnClient = " }";
            deactivate.RefreshSearchView = true;

            return deactivate;
        }

        #endregion

        #region OBSOLETE - Deactivate non-abstract type.

        /// <summary>Creates deactivate/delete command that initializes a business object and calls its Deactivate() method. Uses object's GrantDeactivate() method to enforce security.</summary>
        [Obsolete("Use the following method instead: CreateDeactivateCommand<TBizObject, TFactory>(object, IEnumerable<IDbColumn>, object)")]
        public static BusinessObjectCommand<T> CreateDeactivateCommand<T>(object requiredPermissionForActionId, IDbColumn[] pkFields)
            where T : new()
        {
            BusinessObjectCommand<T> deactivate = new BusinessObjectCommand<T>("Deactivate", UiMessages.DeactivateText);
            deactivate.RequiredPermission = requiredPermissionForActionId;
            foreach (IDbColumn pkPart in pkFields)
                deactivate.MapProperty(pkPart.ColumnName, pkPart.PropertyName);

            T bizObject = new T();
            Type objType = typeof(T);
            Delegate grantDeactivateMethod = Delegate.CreateDelegate(typeof(Delegates.Function<BusinessRuleCollection, IActor>), bizObject, objType.GetMethod("GrantDeactivate"));
            deactivate.SetGrantExecuteMethod((Delegates.Function<BusinessRuleCollection, IActor>)grantDeactivateMethod);
            Delegate deactivateMethod = Delegate.CreateDelegate(typeof(Delegates.Procedure<IActor, bool>), bizObject, objType.GetMethod("Deactivate"));
            deactivate.SetExecuteMethod<bool>((Delegates.Procedure<IActor, bool>)deactivateMethod, false);

            deactivate.PrependToExecuteOnClient = "if (confirm('" + JScript.CleanString(UiMessages.AreYouSure) + "')){ ";
            deactivate.AppendToExecuteOnClient = " }";
            deactivate.RefreshSearchView = true;

            return deactivate;
        }

        /// <summary>Creates deactivate/delete command that initializes a business object and calls its Deactivate() method. Uses object's GrantDeactivate() method to enforce security.</summary>
        [Obsolete("Use the following method instead: CreateDeactivateCommand<TBizObject, TFactory>(object, IEnumerable<IDbColumn>, object)")]
        public static BusinessObjectCommand<T> CreateDeactivateCommand<T>(params IDbColumn[] pkFields)
            where T : new()
        {
            BusinessObjectCommand<T> deactivate = new BusinessObjectCommand<T>("Deactivate", UiMessages.DeactivateText);
            foreach (IDbColumn pkPart in pkFields)
                deactivate.MapProperty(pkPart.ColumnName, pkPart.PropertyName);

            T bizObject = new T();
            Type objType = typeof(T);
            Delegate grantDeactivateMethod = Delegate.CreateDelegate(typeof(Delegates.Function<BusinessRuleCollection, IActor>), bizObject, objType.GetMethod("GrantDeactivate"));
            deactivate.SetGrantExecuteMethod((Delegates.Function<BusinessRuleCollection, IActor>)grantDeactivateMethod);
            Delegate deactivateMethod = Delegate.CreateDelegate(typeof(Delegates.Procedure<IActor, bool>), bizObject, objType.GetMethod("Deactivate"));
            deactivate.SetExecuteMethod<bool>((Delegates.Procedure<IActor, bool>)deactivateMethod, false);

            deactivate.PrependToExecuteOnClient = "if (confirm('" + JScript.CleanString(UiMessages.AreYouSure) + "')){ ";
            deactivate.AppendToExecuteOnClient = " }";
            deactivate.RefreshSearchView = true;

            return deactivate;
        }

        #endregion

        #region OBSOLETE - Deactivate abstract type - factory.

        /// <summary>Creates deactivate/delete command that initializes a business object using factory class and then calls its Deactivate() method. Uses object's GrantDeactivate() method to enforce security.</summary>
        [Obsolete("Use the following method instead: CreateDeactivateCommand<TBizObject, TFactory>(object, IEnumerable<IDbColumn>, object)")]
        public static BusinessObjectFactoryCommand<T> CreateDeactivateCommand<T>(Delegates.Function<T> factoryMethod, IDbColumn[] pkFields)
        {
            BusinessObjectFactoryCommand<T> deactivate = new BusinessObjectFactoryCommand<T>("Deactivate", UiMessages.DeactivateText);
            foreach (IDbColumn pkPart in pkFields)
                deactivate.MapProperty(pkPart.ColumnName, pkPart.PropertyName);

            deactivate.SetFactoryMethod(factoryMethod);

            // Svejedno je kojeg je tipa objekt koji vrati factory. Treba nam samo da kreiramo delegate, konkretna instanca će se otpustiti.
            T bizObject = factoryMethod();
            Type objType = typeof(T);
            Delegate grantDeactivateMethod = Delegate.CreateDelegate(typeof(Delegates.Function<BusinessRuleCollection, IActor>), bizObject, objType.GetMethod("GrantDeactivate"));
            deactivate.SetGrantExecuteMethod((Delegates.Function<BusinessRuleCollection, IActor>)grantDeactivateMethod);
            Delegate deactivateMethod = Delegate.CreateDelegate(typeof(Delegates.Procedure<IActor, bool>), bizObject, objType.GetMethod("Deactivate"));
            deactivate.SetExecuteMethod<bool>((Delegates.Procedure<IActor, bool>)deactivateMethod, false);

            deactivate.PrependToExecuteOnClient = "if (confirm('" + JScript.CleanString(UiMessages.AreYouSure) + "')){ ";
            deactivate.AppendToExecuteOnClient = " }";
            deactivate.RefreshSearchView = true;

            return deactivate;
        }

        /// <summary>Creates deactivate/delete command that initializes a business object using factory class and then calls its Deactivate() method. Uses object's GrantDeactivate() method to enforce security.</summary>
        [Obsolete("Use the following method instead: CreateDeactivateCommand<TBizObject, TFactory>(object, IEnumerable<IDbColumn>, object)")]
        public static BusinessObjectFactoryCommand<T> CreateDeactivateCommand<T, TTypeId>(Delegates.Function<T, TTypeId> factoryMethod, IDbColumn[] pkFields, IDbColumn typeIdField, TTypeId typeExample)
        {
            BusinessObjectFactoryCommand<T> deactivate = new BusinessObjectFactoryCommand<T>("Deactivate", UiMessages.DeactivateText);
            foreach (IDbColumn pkPart in pkFields)
                deactivate.MapProperty(pkPart.ColumnName, pkPart.PropertyName);

            deactivate.SetFactoryMethod<TTypeId>(factoryMethod);
            deactivate.MapFactoryMethodArgument(typeIdField.ColumnName, 0);

            // It doesn't matter which subtype is returned by factory. We only need it to create delegates, concrete instance won't be used.
            T bizObject = factoryMethod(typeExample);
            Type objType = typeof(T);
            Delegate grantDeactivateMethod = Delegate.CreateDelegate(typeof(Delegates.Function<BusinessRuleCollection, IActor>), bizObject, objType.GetMethod("GrantDeactivate"));
            deactivate.SetGrantExecuteMethod((Delegates.Function<BusinessRuleCollection, IActor>)grantDeactivateMethod);
            Delegate deactivateMethod = Delegate.CreateDelegate(typeof(Delegates.Procedure<IActor, bool>), bizObject, objType.GetMethod("Deactivate"));
            deactivate.SetExecuteMethod<bool>((Delegates.Procedure<IActor, bool>)deactivateMethod, false);

            deactivate.PrependToExecuteOnClient = "if (confirm('" + JScript.CleanString(UiMessages.AreYouSure) + "')){ ";
            deactivate.AppendToExecuteOnClient = " }";
            deactivate.RefreshSearchView = true;

            return deactivate;
        }

        #endregion
    }
}
